﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Reflection;
using System.Collections;
using System.Diagnostics;

namespace Ideal.API.LinqTextQueryBuilder
{
    public class LinqTextQueryRuntime<TSource> where TSource : class
    {

        private const string GeneratedClassName = "LinqTextQueryGeneratedClass";
        private const string GeneratedClassQueryMethodName = "Query";


        private IEnumerable<string> _usedAssembliesLocations;
        private IEnumerable<string> _usedNamespaces;
        private string _sourceType;

        public LinqTextQueryRuntime( string queryText, IEnumerable<string> usedAssembliesLocations = null, IEnumerable<string> usedNamespaces = null )
        {
            if ( queryText == null ) throw new ArgumentNullException( "queryText" );
            _usedAssembliesLocations = usedAssembliesLocations;
            _usedNamespaces = usedNamespaces;
            _sourceType = typeof( TSource ).FullName;
            CreateRuntimeAssembly( queryText );
        }

        public Assembly Assembly { get; private set; }

        private string GetNamespacesDefination( IEnumerable<string> namespaces )
        {
            StringBuilder result = new StringBuilder();
            foreach ( var ns in namespaces )
            {
                result.AppendLine( string.Format( "using {0};", ns ) );
            }
            return result.ToString();
        }
        private void CreateRuntimeAssembly( string queryText )
        {
            if ( queryText == null ) throw new ArgumentNullException( "queryText" );

            CodeDomProvider codeProvider = new CSharpCodeProvider();
            CompilerParameters compilerParameters = new CompilerParameters();

            compilerParameters.GenerateExecutable = false;
            compilerParameters.GenerateInMemory = true;
            compilerParameters.IncludeDebugInformation = false;
            compilerParameters.WarningLevel = 3;
            compilerParameters.TreatWarningsAsErrors = false;
            compilerParameters.CompilerOptions = "/optimize";

            string[] defaultAssemblies = { "mscorlib.dll", "System.dll", "System.Core.dll" };
            List<string> usedAssembliesWithDefaults = new List<string>();
            if ( _usedAssembliesLocations != null )
                usedAssembliesWithDefaults.AddRange( _usedAssembliesLocations );
            foreach ( var item in defaultAssemblies )
            {
                if ( usedAssembliesWithDefaults.Where( e => e.ToLower().Contains( item.ToLower() ) ).FirstOrDefault() == null )
                    usedAssembliesWithDefaults.Add( item );
            }

            foreach ( string assemblyLocation in usedAssembliesWithDefaults )
            {
                compilerParameters.ReferencedAssemblies.Add( assemblyLocation );
            }

            string classText = @"
                                using System;
                                using System.Collections;
                                using System.Collections.Generic;
                                using System.Linq;
                                using System.Text;

                                /Namespaces/

                                public class /GeneratedClassName/ {
                                    private /SourceType/ source;

                                    public /GeneratedClassName/( /SourceType/ parmSource )
                                    {
                                        source = parmSource;
                                    }     
                                    public IList /GeneratedClassQueryMethodName/()
                                    {
                                        var queryResult = /QueryText/;
                                        return queryResult.ToList();
                                    }                
                                }";
            // setting namespaces. if 'null' then replace with String.Empty
            if ( _usedNamespaces != null )
                classText = classText.Replace( "/Namespaces/", GetNamespacesDefination( _usedNamespaces ) );
            else
                classText = classText.Replace( "/Namespaces/", string.Empty );
            // setting class name
            classText = classText.Replace( "/GeneratedClassName/", GeneratedClassName );
            // setting source type
            classText = classText.Replace( "/SourceType/", _sourceType );
            // setting query method name
            classText = classText.Replace( "/GeneratedClassQueryMethodName/", GeneratedClassQueryMethodName );
            // setting query
            classText = classText.Replace( "/QueryText/", queryText );

            try
            {
                CompilerResults compileResults = codeProvider.CompileAssemblyFromSource( compilerParameters, new string[] { classText } );
                if ( compileResults.Errors.HasErrors )
                {
                    var errors = compileResults.Errors.Cast<CompilerError>().Select( e => new LinqTextQueryRuntimeError( e ) );
                    throw new LinqTextQueryRuntimeException( "Some error occurred during creating of runtime assembly", errors );
                }
                Assembly = compileResults.CompiledAssembly;
            }
            catch ( Exception exp )
            {
                throw exp;
            }
        }

        public IList ExecuteQuery( TSource source )
        {
            if ( source == null ) throw new ArgumentNullException( "source" );
            if ( Assembly == null ) throw new LinqTextQueryRuntimeException( "Some error occurred during creating of runtime assembly" );

            Type queryClass = Assembly.GetType( GeneratedClassName );
            var queryMethod = queryClass.GetMethod( GeneratedClassQueryMethodName );

            var queryClassInstance = Activator.CreateInstance( queryClass, new[] { source } );
            var queryResult = queryMethod.Invoke( queryClassInstance, null );
            return queryResult as IList;
        }

    }
}
